package com.sojson.util.filter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpResponse;

import com.sojson.config.exception.TransErrorCode;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantByMap;
import com.sojson.result.interfaces.BeforeBodyWriteInterface;
import com.sojson.result.interfaces.FilterResponseDataInterface;
import com.sojson.result.interfaces.impl.BeforeBodyWriteInterfaceImpl;
import com.sojson.result.interfaces.impl.BeforeBodyWriteIsStringInterfaceImpl;
import com.sojson.result.interfaces.impl.filter.FilterResponseDataByOut;
import com.sojson.result.interfaces.impl.filter.FilterResponseDataByOutOrRedirect;
import com.sojson.result.interfaces.impl.filter.FilterResponseDataByOutOrSaveUrlAndRedirect;
import com.sojson.result.interfaces.impl.filter.FilterResponseDataByRedirect;
import com.sojson.result.interfaces.impl.filter.FilterResponseDataBySaveUrlAndRedirect;
import com.sojson.util.ExpUtil;
import com.sojson.util.INI4jUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.servlet.ServletUtil;
import com.sojson.util.text.Convert;
import com.sojson.util.token.TokenUtil;
import com.sojson.util.url.UrlUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * shiro过滤器工具类
 * 
 * @author liu
 * @Date 2020-06-06 10:02:59
 * @Description
 *
 */
@Slf4j
public class FilterUtil {

    private static FilterResponseDataInterface FILTER_RESPONSE_DATA_INTERFACE = getFilterResponseDataInterface();
    public static BeforeBodyWriteInterface BEFORE_BODY_WRITE = getBeforeBodyWriteInterface();
    private static INI4jUtil permissionIni;
    /** 过滤器最小执行顺序 */
    public static final Integer MIN_ORDER = 0;
    /** 权限列表文件的位置 */
    private static final String PERM_LIST_FILE_PATH =
        Convert.toStr(Constant.PROPERTIES.getProperty("so.filename.permList"), "perm-list.ini");

    /**
     * 对结果进行处理
     * 
     * @param request
     * @param response
     * @param code 错误代码
     * @param url 要跳转的路径
     * @param logout 表示退出登录,只是表示要掉这个方法,没其他作用
     * @throws IOException
     */
    public static void responseDate(ServletRequest request, ServletResponse response, TransErrorCode code, String url)
        throws IOException {
        FILTER_RESPONSE_DATA_INTERFACE.responseDate(request, response, code, url);
    }

    /**
     * 对结果进行处理并更新Session
     * 
     * @param request
     * @param response
     * @param code 错误代码
     * @param url 要跳转的路径
     * @throws IOException
     */
    public static void responseDateTouch(ServletRequest request, ServletResponse response, TransErrorCode code,
        String url) throws IOException {
        // 更新Session
        sessionTouch();
        responseDate(request, response, code, url);
    }

    /**
     * 对结果进行处理并退出登录
     * 
     * @param request
     * @param response
     * @param code 错误代码
     * @param url 要跳转的路径
     * @param logout 表示退出登录,只是表示要掉这个方法,没其他作用
     * @throws IOException
     */
    public static void responseDateLogout(ServletRequest request, ServletResponse response, TransErrorCode code,
        String url) throws IOException {
        // 先退出
        TokenUtil.logout();
        responseDate(request, response, code, url);
    }

    /**
     * 是否是Ajax请求
     * 
     * @param request
     * @return
     */
    public static boolean isAjax(ServletRequest request) {
        return "XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest)request).getHeader("X-Requested-With"));
    }

    /**
     * 初始化ServerHttpResponse
     * 
     * @param response
     * @throws IOException
     */
    public static void initServerHttpResponse(ServerHttpResponse response) {

    }

    /**
     * 重定向到指定页面
     * 
     * @param request
     * @param response
     * @throws IOException
     */
    public static void redirect(ServletRequest request, ServletResponse response, String url) throws IOException {
        ServletUtil.redirect((HttpServletResponse)response, url);
    }

    /**
     * 保存要访问的地址并重定向到指定页面
     * 
     * @param request
     * @param response
     * @param url 要重定向的地址
     * @throws IOException
     */
    public static void saveUrlAndRedirect(ServletRequest request, ServletResponse response, String url)
        throws IOException {
        ServletUtil.redirect((HttpServletResponse)response, url);
    }

    /**
     * 更新Session
     * 
     */
    public static void sessionTouch() {
        // 以下是照抄shiro的每次有请求都更新一次Session,shiro的更新Session太早了,所以我把shiro的注释了,放在了这里
        // System.out.println(1);
        // if (!ConstantBean.SECURITY_MANAGER.isHttpSessionMode()) {
        // System.out.println("2: 正在获取Subject");
        // Subject subject = SecurityUtils.getSubject();
        // if (subject != null) {
        // System.out.println("3: 正在获取Session");
        // Session session = subject.getSession(false);
        // if (session != null) {
        // System.out.println("4: 正在更新Session");
        // session.touch();
        // }
        // }
        // }
    }

    /**
     * 开放目录(为了让每次访问都少拼接一次"/")
     */
    private static final String OPEN_URL_REDIRECT =
        ServletUtil.getMappingUrl(ConstantByMap.CONTROLLER_MAPPING_OPEN_OPEN + "/");

    /**
     * 是不是开放目录
     * 
     * @param request
     * @return
     */
    public static boolean isOpenUrl(HttpServletRequest request) {
        // 如果是相关目录return true
        String uri = getRequestMapping(request);
        // String basePath = httpRequest.getContextPath();// 获取basePath
        // if (uri.startsWith(basePath)) {
        // uri = uri.replaceFirst(basePath, "");
        // }
        return uri.startsWith(OPEN_URL_REDIRECT);
    }

    /**
     * 路径去掉最后的/
     * 
     * @param str
     * @return
     */
    public static String removeUriDblique(String str) {
        return str;
    }

    /**
     * 路径最后添加/
     * 
     * @param str
     * @return
     */
    public static String addUriDblique(String str) {
        return str;
    }

    /** 过滤器获取拦截路径 */
    public static final int FILTER = 1;
    /** 拦截器获取拦截路径 */
    public static final int INTERCEPTOR = 2;

    /**
     * 
     * @param type 哪种过滤器
     * @return
     */
    public static ArrayList<String> getPathPatterns(int type) {
        ArrayList<String> arrayList = new ArrayList<>();

        if (FILTER == type) {
            arrayList.add(ServletUtil.METHOD_PATH + "/*");
        } else if (INTERCEPTOR == type) {
            arrayList.add(ServletUtil.METHOD_PATH + "/**");
        }
        return arrayList;
    }

    /**
     * 获取权限列表文件的内容
     * 
     * @return
     * @throws IOException
     */
    public static INI4jUtil getIni4jUtil() throws IOException {
        if (isNotBlankObject(FilterUtil.permissionIni)) {
            return FilterUtil.permissionIni;
        }

        // 用这种方式读取文件是因为多模块项目要加载jar包中的文件,普通路径方式满足不了
        // InputStream resourceAsStream = CustomShiroManager.class.getClassLoader().getResourceAsStream(shiroBaseAuth);
        InputStream resourceAsStream = new ClassPathResource(PERM_LIST_FILE_PATH).getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        try {
            FilterUtil.permissionIni = new INI4jUtil(bufferedReader);
        } catch (IOException e) {
            log.error("加载文件出错。file:[%s]", PERM_LIST_FILE_PATH);
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (Exception e2) {
                    log.error(e2.getMessage(), e2);
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (Exception e2) {
                    log.error(e2.getMessage(), e2);
                }
            }
            if (resourceAsStream != null) {
                try {
                    resourceAsStream.close();
                } catch (Exception e2) {
                    log.error(e2.getMessage(), e2);
                }
            }
        }
        return FilterUtil.permissionIni;
    }

    /**
     * 判断当前路径是否不过滤(过滤器无法设置不过滤哪些路径,要自己做)
     * 
     * @return
     */
    public static boolean isExcludePath(HashSet<String> excludePath) {
        String requestMapping = getRequestMapping();
        for (String string : excludePath) {
            string = string.replace("*", "**");
            boolean match = UrlUtil.match(string, requestMapping);
            if (match) {
                return match;
            }
        }
        return false;
    }

    /**
     * 判断是否是字符串请求
     *
     * @param request
     */
    public static boolean isHttpString(HttpServletRequest request) {
        String header = request.getHeader(HttpHeaders.CONTENT_TYPE);
        if (StringUtil.isBlankObject(header)) {
            return true;
        }

        boolean isString = false;
        String splitStr = ";";
        if (header.indexOf(splitStr) > 0) {
            String[] split = header.split(splitStr);
            for (String string : split) {
                boolean is = isHttpString(string);
                if (is) {
                    isString = true;
                    break;
                }
            }
        } else {
            isString = isHttpString(header);
        }

        return isString;
    }

    /**
     * 判断是否是字符串请求
     *
     * @param contentType
     */
    public static boolean isHttpString(String contentType) {
        return MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType)
            || MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)
            || MediaType.TEXT_PLAIN_VALUE.equalsIgnoreCase(contentType)
            || MediaType.TEXT_HTML_VALUE.equalsIgnoreCase(contentType)
            || MediaType.TEXT_XML_VALUE.equalsIgnoreCase(contentType)
            || MediaType.APPLICATION_XML_VALUE.equalsIgnoreCase(contentType);
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    /**
     * 获取客户端访问的地址
     * 
     * @return
     */
    private static String getRequestMapping() {
        return ServletUtil.getRequestMapping();
    }

    /**
     * 获取客户端访问的地址
     * 
     * @param request
     * @return
     */
    private static String getRequestMapping(HttpServletRequest request) {
        return ServletUtil.getRequestMapping(request);
    }

    /**
     * 初始化过滤器拦截后做的操作
     * 
     * @return
     */
    private static FilterResponseDataInterface getFilterResponseDataInterface() {
        if (FilterResponseDataInterface.OUT == FilterResponseDataInterface.FILTER_RESPONSE_DATA_TYPE) {
            return FilterResponseDataByOut.getInstances();
        } else if (FilterResponseDataInterface.REDIRECT == FilterResponseDataInterface.FILTER_RESPONSE_DATA_TYPE) {
            return FilterResponseDataByRedirect.getInstances();
        } else if (FilterResponseDataInterface.SAVE_URL_AND_REDIRECT == FilterResponseDataInterface.FILTER_RESPONSE_DATA_TYPE) {
            return FilterResponseDataBySaveUrlAndRedirect.getInstances();
        } else if (FilterResponseDataInterface.OUT_OR_REDIRECT == FilterResponseDataInterface.FILTER_RESPONSE_DATA_TYPE) {
            return FilterResponseDataByOutOrRedirect.getInstances();
        } else if (FilterResponseDataInterface.OUT_OR_SAVE_URL_AND_REDIRECT == FilterResponseDataInterface.FILTER_RESPONSE_DATA_TYPE) {
            return FilterResponseDataByOutOrSaveUrlAndRedirect.getInstances();
        }
        ExpUtil.throwEx(TransErrorCode.PROPERTIES_CONFIGURATION_NO_CORRECT);
        return null;
    }

    /**
     * 初始返回数据的类型如果为字符串,转换前是否先判断是不是字符串(不设置为true)
     * 
     * @return
     */
    private static BeforeBodyWriteInterface getBeforeBodyWriteInterface() {
        if (BeforeBodyWriteInterface.RESPONSE_DATA_TYPE_IS_STRING) {
            return BeforeBodyWriteIsStringInterfaceImpl.getInstances();
        } else {
            return BeforeBodyWriteInterfaceImpl.getInstances();
        }
    }

}